package com.tonyodev.fetch2core.server;

import ohos.utils.zson.ZSONObject;

import com.tonyodev.fetch2core.Extras;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.LinkedHashMap;

/**
 * FetchFileResourceTransporter
 *
 * @since 2021-05-20
 */
public final class FetchFileResourceTransporter implements FileResourceTransporter {
    private final Socket client;
    private DataInputStream dataInput;
    private DataOutputStream dataOutput;
    private volatile boolean closed = false;
    private Object lock = new Object();

    /**
     * 构造函数
     *
     * @param client Socket
     */
    public FetchFileResourceTransporter(Socket client) {
        this.client = client;
        init();
    }

    /**
     * 构造函数
     */
    public FetchFileResourceTransporter() {
        this.client = new Socket();
        init();
    }

    private void init() {
        try {
            if (client.isConnected() && !client.isClosed()) {
                dataInput = new DataInputStream(client.getInputStream());
                dataOutput = new DataOutputStream(client.getOutputStream());
            }
            if (client.isClosed()) {
                closed = true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Socket getClient() {
        return client;
    }

    @Override
    public boolean isClosed() {
        synchronized (lock) {
            return closed;
        }
    }

    @Override
    public void connect(@NotNull SocketAddress socketAddress) {
        synchronized (lock) {
            throwExceptionIfClosed();
            try {
                client.connect(socketAddress);
                dataInput = new DataInputStream(client.getInputStream());
                dataOutput = new DataOutputStream(client.getOutputStream());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public @Nullable FileRequest receiveFileRequest() {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            ZSONObject json = null;
            try {
                json = ZSONObject.stringToZSON(dataInput.readUTF());
            } catch (IOException e) {
                e.printStackTrace();
            }
            int requestType = json.getIntValue(FileRequest.FIELD_TYPE);
            String fileResourceId = json.getString(FileRequest.FIELD_FILE_RESOURCE_ID);
            long rangeStart = json.getLong(FileRequest.FIELD_RANGE_START);
            long rangeEnd = json.getLong(FileRequest.FIELD_RANGE_END);
            String authorization = json.getString(FileRequest.FIELD_AUTHORIZATION);
            String client = json.getString(FileRequest.FIELD_CLIENT);
            Extras extras;
            try {
                LinkedHashMap map = new LinkedHashMap<String, String>();
                ZSONObject jsonObject = ZSONObject.stringToZSON(json.getString(FileRequest.FIELD_EXTRAS));
                jsonObject.keySet().forEach((key) -> {
                    map.put(key, jsonObject.getString(key));
                });
                extras = new Extras(map);
            } catch (Exception e) {
                extras = Extras.getEmptyExtras();
            }
            int page = json.getIntValue(FileRequest.FIELD_PAGE);
            int size = json.getIntValue(FileRequest.FIELD_SIZE);
            if ((rangeStart < 0L || rangeStart > rangeEnd) && rangeEnd > -1) {
                rangeStart = 0L;
            }
            if (rangeEnd < 0L || rangeEnd < rangeStart) {
                rangeEnd = -1L;
            }
            if (page < -1) {
                page = -1;
            }
            if (size < -1) {
                size = -1;
            }
            boolean persistConnection = Boolean.valueOf(json.getBoolean(FileRequest.FIELD_PERSIST_CONNECTION));
            return new FileRequest(
                requestType,
                fileResourceId,
                rangeStart,
                rangeEnd,
                authorization,
                client,
                extras,
                page,
                size,
                persistConnection);
        }
    }

    @Override
    public @Nullable FileResponse receiveFileResponse() {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            ZSONObject json = null;
            try {
                json = ZSONObject.stringToZSON(dataInput.readUTF().toLowerCase());
            } catch (IOException e) {
                e.printStackTrace();
            }
            int status = json.getIntValue(FileResponse.FIELD_STATUS);
            int requestType = json.getIntValue(FileResponse.FIELD_TYPE);
            int connection = json.getIntValue(FileResponse.FIELD_CONNECTION);
            long date = json.getLong(FileResponse.FIELD_DATE);
            long contentLength = json.getLong(FileResponse.FIELD_CONTENT_LENGTH);
            String md5 = json.getString(FileResponse.FIELD_MD5);
            String sessionId = json.getString(FileResponse.FIELD_SESSION_ID);
            return new FileResponse(
                status,
                requestType,
                connection,
                date,
                contentLength,
                md5,
                sessionId);
        }
    }

    @Override
    public int readRawBytes(@NotNull byte[] byteArray, int offset, int length) {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            try {
                return dataInput.read(byteArray, offset, length);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return 0;
        }
    }

    @Override
    public @NotNull InputStream getInputStream() {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            return dataInput;
        }
    }

    @Override
    public @NotNull OutputStream getOutputStream() {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            return dataOutput;
        }
    }

    @Override
    public void close() {
        synchronized (lock) {
            if (!closed) {
                closed = true;
                try {
                    dataInput.close();
                } catch (Exception e) {
                }
                try {
                    dataOutput.close();
                } catch (Exception e) {
                }
                try {
                    client.close();
                } catch (Exception e) {
                }
            }
        }
    }

    @Override
    public void sendFileRequest(@NotNull FileRequest fileRequest) throws IOException {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            try {
                dataOutput.writeUTF(fileRequest.getToJsonString());
                dataOutput.flush();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void sendFileResponse(@NotNull FileResponse fileResponse) throws IOException {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            dataOutput.writeUTF(fileResponse.getToJsonString());
            dataOutput.flush();
        }
    }

    @Override
    public void sendRawBytes(@NotNull byte[] byteArray, int offset, int length) throws IOException {
        synchronized (lock) {
            throwExceptionIfClosed();
            throwIfNotConnected();
            dataOutput.write(byteArray, offset, length);
            dataOutput.flush();
        }
    }

    private void throwExceptionIfClosed() {
        if (closed) {
            try {
                throw new Exception("FetchFileResourceTransporter is already closed.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @SuppressWarnings("SENSELESS_COMPARISON")
    private void throwIfNotConnected() {
        if (dataInput == null || dataOutput == null) {
            try {
                throw new Exception("You forgot to call connect before calling this method.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
